/*
 * Inlcude Section
 */


#include "kernel.h"
#include <signal.h>
#include <minix/callnr.h>
#include <minix/com.h>
#include <minix/keymap.h>
#include <ibm/int86.h>
#include "proc.h"
#include <unistd.h>

/*
 *		Define Section
 *
 */

#define MOUSE_HAS_READ 0
#define MOUSE_READ 1
#define MOUSE_WRITE 2
#define MOUSE_BUFFER_SIZE 2

#define FIRST_PACKAGE 0
#define SECOND_PACKAGE 1
#define THIRD_PACKAGE 2


FORWARD _PROTOTYPE(void do_open, (message * m_ptr) );
FORWARD _PROTOTYPE(void do_close, (message * m_ptr) );
FORWARD _PROTOTYPE(void do_write, (message *m_ptr) );
FORWARD _PROTOTYPE(void do_read, (message *m_ptr) );
FORWARD _PROTOTYPE(void do_ioctl, (message *m_ptr) );
FORWARD _PROTOTYPE( void mouse_reply, (int replyee, int proc_nr, int status, mouseevent_t * event, char * buffer) );

static mouseevent_t mouseBuffer[MOUSE_BUFFER_SIZE];
static int mBufferHead, mBufferTail;
static int isMouseOpened = FALSE;

static void mouse_int_served(void){
	outb(0xa0, 0x20);
	outb(0x20, 0x20);
}

/*
 * Mouse Buffer Functions
 */
static int isMouseBufferFull(void) {
	return ((mBufferTail + 1) % MOUSE_BUFFER_SIZE) == mBufferHead;
}

static int enqueueMouseEvt(mouseevent_t *evt) {
	if ( isMouseBufferFull() )
		return FALSE;
	mouseBuffer[mBufferTail] = *evt;
	mBufferTail = (mBufferTail + 1) % MOUSE_BUFFER_SIZE;
	return TRUE;
}

static int dequeueMouseEvt(mouseevent_t *evt) {
	if ( evt == NULL )
		return FALSE;
	if ( mBufferHead == mBufferTail )
		return FALSE;
	*evt = mouseBuffer[mBufferHead];
	mBufferHead = (mBufferHead + 1) % MOUSE_BUFFER_SIZE;
	return TRUE;
}

static int sizeMQ(void){
    return (mBufferHead > mBufferTail)? MOUSE_BUFFER_SIZE - (mBufferTail > mBufferHead) : (mBufferTail > mBufferHead);
}

/*===========================================================================*
 *				mouse_reply                                                   *
 *===========================================================================*/
PRIVATE void mouse_reply(int replyee, int proc_nr, int status, mouseevent_t *event, char *buffer) {
	message mouse_mess;
	mouseevent_t *replyEvent;    

	mouse_mess.m_type = TASK_REPLY;
	mouse_mess.REP_PROC_NR = proc_nr;
	
	if ( status == OK && event != NULL && buffer != NULL ) {
		mouse_mess.REP_STATUS = sizeof(mouseevent_t);
        sys_copy(MOUSE, D, (phys_bytes)event, proc_nr, D, (phys_bytes)buffer, (phys_bytes)sizeof(mouseevent_t));
	} else
		mouse_mess.REP_STATUS = status;

	if ((status = send(replyee, &mouse_mess)) != OK)
		panic("mouse_reply failed, status\n", status);
}

/*===========================================================================*
 *				mouse_task                                                   *
 *===========================================================================*/
PUBLIC void mouse_task(void) {
  /* Main routine of the mouse task. */

  message mouse_mess;		/* buffer for all incoming messages */
  
  /* Display the Mouse startup banner. */
  printf("Mouse driver enabled.\n\n");

  while (TRUE)
  {
	receive(ANY, &mouse_mess);

	/* A hardware interrupt is an invitation to check for events. */
	if (mouse_mess.m_type == HARD_INT)
		continue;

	/* Execute the requested function. */
	switch (mouse_mess.m_type)
	{
		case DEV_OPEN:
			do_open(&mouse_mess);
			break;
		case DEV_CLOSE:
			do_close(&mouse_mess);
			break;
		case DEV_READ:
	    	do_read(&mouse_mess);
		   	break;
		case DEV_WRITE:
	    	do_write(&mouse_mess);
	    	break;
		case DEV_IOCTL:
		   	do_ioctl(&mouse_mess);
		   	break; 
		default:
		   	mouse_reply(mouse_mess.m_source, mouse_mess.PROC_NR, EINVAL, NULL, NULL);
			break;
	}
  }
}


/*===========================================================================*
 *				do_read					     *
 *===========================================================================*/
PRIVATE void do_read(message *m_ptr) {
	int r;
	mouseevent_t event;
    

	if ( m_ptr->COUNT == sizeof(mouseevent_t) && dequeueMouseEvt(&event) ){
		mouse_reply(m_ptr->m_source, m_ptr->PROC_NR, OK, &event, m_ptr->ADDRESS);
    }
	else {
		mouse_reply(m_ptr->m_source, m_ptr->PROC_NR, EINVAL, NULL, NULL);
    }
}

/*===========================================================================*
 *				do_write				     *
 *===========================================================================*/
PRIVATE void do_write(register message *m_ptr) {
	mouse_reply(m_ptr->m_source, m_ptr->PROC_NR, EINVAL, NULL, NULL);
}


/*===========================================================================*
 *				do_open                                                      *
 *===========================================================================*/
PRIVATE void do_open(message *m_ptr) {
	if ( isMouseOpened ) {
		mouse_reply(m_ptr->m_source, m_ptr->PROC_NR, EINVAL, NULL, NULL);
	} else {
		mBufferTail = mBufferHead = 0;
		isMouseOpened = TRUE;
		mouse_reply(m_ptr->m_source, m_ptr->PROC_NR, OK, NULL, NULL);
	}
}


/*===========================================================================*
 *				do_close				     *
 *===========================================================================*/
PRIVATE  void do_close(message *m_ptr) {
    isMouseOpened = FALSE;
    mBufferTail = mBufferHead = 0; /* Flush mouse buffer */
	mouse_reply(m_ptr->m_source, m_ptr->PROC_NR, OK, NULL, NULL);
}


/*===========================================================================*
 *				do_ioctl	      				     	 *
 *===========================================================================*/
PRIVATE void do_ioctl(message *m_ptr) {
	mouse_reply(m_ptr->m_source, m_ptr->PROC_NR, EINVAL, NULL, NULL);
} 

static void mouseWait(unsigned char option) {
	unsigned int time = 10000;
	while( time )
	{	
		if(option == MOUSE_WRITE)
		{
			if(((inb(0x64)& 2) == 0))
				return;
		}
		if(option == MOUSE_READ)
		{
			if(((inb(0x64)& 1) == 1))
				return;
		}
		if(option == MOUSE_HAS_READ)
		{
			if( (inb(0x64) == 0xFA))
				return;
		}
		--time;
	}
}

static void mouseWrite(unsigned char a) {
	mouseWait(MOUSE_WRITE);
	outb(0x64, 0xD4);
	mouseWait(MOUSE_WRITE);
	outb(0x60,a);
	mouseWait(MOUSE_HAS_READ);
	inb(0x60);
	return;
}

static int mouseIsReady(void ) {
	int status;
	int time = 100000;
	while( time)
 	{
		if((((status = inb(0x64)) & 0x21)== 0x21) )
			return TRUE;
		time--;
	}
	return FALSE;	

}


int mouseHandler(irq_hook_t *hook ) {
	
	mouseevent_t evt;
    static int flags = 0;
	static unsigned char X_delta,Y_delta;

	static int state = FIRST_PACKAGE;


	switch (state) {
		case FIRST_PACKAGE:
			flags = inb(0x60);				
			mouse_int_served();
			state = SECOND_PACKAGE;
			break;
		case SECOND_PACKAGE:
			X_delta = inb(0x60);
			mouse_int_served();
			state = THIRD_PACKAGE;
			break;
		case THIRD_PACKAGE:
			Y_delta = inb(0x60);
			mouse_int_served();
			evt.dx = 0;
			evt.dy = 0;
			evt.rightClick = 0;
			evt.leftClick = 0;
			
		 
			if((flags & 0xC0) != 0)
			{
				state = FIRST_PACKAGE;
				return -1;
			}
			
			if(X_delta > 0)
			{
				if((flags & 16) == 0)
				{
					evt.dx= X_delta;
				}
				else
				{
					evt.dx = 255-X_delta;
		            evt.dx *= -1;
				}
			}
			if(Y_delta > 0)
			{
				if((flags & 32) == 0){
					evt.dy = Y_delta;
		            evt.dy *= -1;
		        }
				else
					evt.dy = 255-Y_delta;
			}
			if(flags & 1){
				evt.leftClick = 1;
			}
			if( flags & 2)
				evt.rightClick = 1;
			
		    if ( !isMouseOpened ) {
		    	state = FIRST_PACKAGE;
		        return 1;   
		    }
		    
			enqueueMouseEvt(&evt);
		default:
			state = FIRST_PACKAGE;
			return -1;
			break;
	}
	return 1;
}

void mouseinit(void) {
	static irq_hook_t mouse_hook;
	unsigned char status;

	mBufferTail = mBufferHead = 0;

	mouseWait(MOUSE_WRITE);
	outb(0x64,0xA8);
	mouseWait(MOUSE_HAS_READ);
	mouseWait(MOUSE_WRITE);
	outb(0x64, 0x20);
	mouseWait(MOUSE_HAS_READ);
	mouseWait(MOUSE_READ);
	status = (inb(0x60) | 2);
	mouseWait(MOUSE_WRITE);
	outb(0x64,0x60);
	mouseWait(MOUSE_HAS_READ);
	mouseWait(MOUSE_WRITE);
	outb(0x60, status);
	mouseWrite(0xF6);
	mouseWrite(0xF4);
	put_irq_handler(&mouse_hook, MOUSE_IRQ, mouseHandler);
	enable_irq(&mouse_hook);
	return;
}
